#include <string.h>
#include <stdlib.h>
#include <stdbool.h>

#include "user_common_inner.h"
#include "list.h"
#include "user_threads.h"
#include "user_threads_inner.h"
#include "user_mutex.h"

#include <assert.h>

#define START_MAGIC 0xB00FBEEF

struct umutex_struct
{
	/* the threads waiting queue to lock() the mutex. */
	list wq;

	/* MUTEX_UNLOCKED if unlocked, MUTEX_FAILURE if became unusable... */
	int state;

	/* the currrently locking thread. -1 if not locked or unusable*/
	thread_id locking_thread_id;

	/* the list and iterator of the mutex in the locking thread's locks list */
	list * locking_thread_list;
	iterator locking_thread_iter;
};

/* internal mutex unlock, to enable the user_thread to unlock a specific thread's mutexes on exit() */
static int unlock_mutex(uthread_mutex_t mutex, thread_id thread);

/* is the mutex initialized? returns an iterator to the mutexes list if it is */
static bool is_mutex_initialized(uthread_mutex_t mutex, iterator * iter);

/* returns the head of the thread queue */
static thread_id get_locking_thread(uthread_mutex_t mutex);

/* is the mutex still usable? */
static bool is_mutex_unusable(uthread_mutex_t mutex);

/* validate mutex integrity for debugging */
static void validate_mutex(uthread_mutex_t mutex);

/* reset mutex on unlock */
static void reset_locking(uthread_mutex_t mutex);

/* adds the mutex to locks list */
static int lock_internal(thread_id tid, uthread_mutex_t m);

/* removes the mutex from locks list */
static int unlock_internal(uthread_mutex_t m);

/* all the created mutexes... to make sure a given mutex is initalized, or not... */
list mutexes;

/*************************************************************/

int mutexes_init()
{
	int lrc = LIST_ERROR;

	lrc = list_init(&mutexes);
	if (LIST_ERROR == lrc)
	{
		return MUTEX_FAILURE;
	}

	return MUTEX_SUCCESS;
}

int uthread_mutex_init(uthread_mutex_t* mutex)
{
	int ret = MUTEX_FAILURE;
	int lrc = LIST_ERROR;

	uthread_init();
	uthread_lock();

	if (NULL != mutex)
	{
		if (is_mutex_initialized(*mutex, NULL))
		{
			ret = MUTEX_FAILURE;
			goto out;
		}
	}

	*mutex = malloc(sizeof(**mutex));
	if (NULL == *mutex)
	{
		ret = MUTEX_FAILURE;
		goto error;
	}

	lrc = list_init(&((*mutex)->wq));
	if (LIST_ERROR == lrc)
	{
		ret = MUTEX_FAILURE;
		goto error;
	}

	/* reset_locking() will initialize state */
	(*mutex)->state = MUTEX_UNINITIALIZED;
	reset_locking(*mutex);

	lrc = list_insert_head(&mutexes, *mutex);
	if (LIST_ERROR == lrc)
	{
		ret = MUTEX_FAILURE;
		goto error;
	}

	validate_mutex(*mutex);

	ret = MUTEX_SUCCESS;
	goto out;

error:
	list_destroy(&((*mutex)->wq));
	free(*mutex);
out:
	uthread_unlock();
	return ret;
}

int uthread_mutex_destroy(uthread_mutex_t mutex)
{
	int ret = MUTEX_FAILURE;
	iterator iter;

	uthread_init();
	uthread_lock();
	validate_mutex(mutex);

	if (NULL == mutex)
	{
		uthread_unlock();
		return MUTEX_INVALID;
	}

	if (!is_mutex_initialized(mutex, &iter))
	{
		uthread_unlock();
		return MUTEX_UNINITIALIZED;
	}

	if (is_mutex_unusable(mutex))
	{
		uthread_unlock();
		return MUTEX_FAILURE;
	}

	if ((MUTEX_LOCKED == mutex->state) || (0 != list_get_size(&mutex->wq)))
	{
		ret = MUTEX_LOCKED;
		goto out;
	}

	list_destroy(&mutex->wq);

	free(mutex);

	list_remove(&mutexes, &iter);

	ret = MUTEX_SUCCESS;
	out: uthread_unlock();
	return ret;
}

int uthread_mutex_lock(uthread_mutex_t mutex)
{
	int ret = MUTEX_SUCCESS;

	uthread_init();
	uthread_lock();
	validate_mutex(mutex);

	if (NULL == mutex)
	{
		uthread_unlock();
		return MUTEX_INVALID;
	}

	if (!is_mutex_initialized(mutex, NULL))
	{
		uthread_unlock();
		return MUTEX_UNINITIALIZED;
	}

	if (is_mutex_unusable(mutex))
	{
		uthread_unlock();
		return MUTEX_FAILURE;
	}

	bool need_to_suspend = false;

	if (MUTEX_LOCKED == mutex->state)
	{
		if (uthread_self() == get_locking_thread(mutex))
		{
			ret = MUTEX_FAILURE;
			goto out;
		}
		else
		{
			need_to_suspend = true;
		}
	}
	else
	{
		if (UTHREAD_SUCCESS != lock_internal(uthread_self(), mutex))
		{
			ret = MUTEX_FAILURE;
			goto out;
		}
	}

	out: if (need_to_suspend)
	{
		uthread_suspend_self(&mutex->wq);
		/* since we are in a non-prioritized FIFO, once we were awoken, we should be the locking thread */
		assert(get_locking_thread(mutex) == uthread_self());
	}
	else
	{
		/* we need to resume the sig only if we weren't suspended and rescheduled */
		uthread_unlock();
	}
	return ret;
}

int uthread_mutex_try_lock(uthread_mutex_t mutex)
{
	int ret = MUTEX_SUCCESS;

	uthread_init();
	uthread_lock();
	validate_mutex(mutex);

	if (NULL == mutex)
	{
		uthread_unlock();
		return MUTEX_INVALID;
	}

	if (!is_mutex_initialized(mutex, NULL))
	{
		uthread_unlock();
		return MUTEX_UNINITIALIZED;
	}

	if (is_mutex_unusable(mutex))
	{
		uthread_unlock();
		return MUTEX_FAILURE;
	}

	if (MUTEX_LOCKED == mutex->state)
	{
		if (uthread_self() == get_locking_thread(mutex))
		{
			ret = MUTEX_FAILURE;
		}
		else
		{
			ret = MUTEX_LOCKED;
		}
	}
	else
	{
		if (UTHREAD_SUCCESS != lock_internal(uthread_self(), mutex))
		{
			ret = MUTEX_FAILURE;
		}
	}

	uthread_unlock();

	return ret;
}

int uthread_mutex_unlock(uthread_mutex_t mutex)
{
	uthread_init();

	validate_mutex(mutex);

	return unlock_mutex(mutex, uthread_self());
}

void mutex_set_zombie(uthread_mutex_t mutex)
{
	validate_mutex(mutex);
	assert(MUTEX_LOCKED == mutex->state);
	reset_locking(mutex);
	mutex->state = MUTEX_FAILURE;
}

static int unlock_mutex(uthread_mutex_t mutex, thread_id thread)
{
	int ret = MUTEX_INVALID;

	uthread_lock();
	validate_mutex(mutex);

	if (NULL == mutex)
	{
		ret = MUTEX_INVALID;
		goto out;
	}

	if (!is_mutex_initialized(mutex, NULL))
	{
		ret = MUTEX_UNINITIALIZED;
		goto out;
	}

	if (is_mutex_unusable(mutex))
	{
		return MUTEX_FAILURE;
	}

	if (MUTEX_UNLOCKED == mutex->state)
	{
		ret = MUTEX_UNLOCKED;
		goto out;

	}
	if (thread != get_locking_thread(mutex))
	{
		ret = MUTEX_LOCKED;
		goto out;
	}

	if (UTHREAD_SUCCESS != unlock_internal(mutex))
	{
		ret = MUTEX_FAILURE;
		goto out;
	}

	ret = MUTEX_SUCCESS;

out:
	uthread_unlock();
	return ret;
}

static bool is_mutex_initialized(uthread_mutex_t mutex, iterator * out_iter)
{
	iterator iter;
	int lrc = LIST_ERROR;

	lrc = list_find(&mutexes, mutex, &iter);

	if (NULL != out_iter)
	{
		*out_iter = iter;
	}

	return LIST_SUCCESS == lrc;
}

static bool is_mutex_unusable(uthread_mutex_t mutex)
{
	validate_mutex(mutex);
	return (MUTEX_FAILURE == mutex->state);
}

static thread_id get_locking_thread(uthread_mutex_t mutex)
{
	return mutex->locking_thread_id;
}

static void validate_mutex(uthread_mutex_t mutex)
{
	if (NULL != mutex)
	{
		if (is_mutex_initialized(mutex, NULL))
		{
			if (MUTEX_LOCKED == mutex->state)
			{
				uthread_mutex_t m = NULL;
				assert(-1 != mutex->locking_thread_id);
				assert(NULL != mutex->locking_thread_list);
				assert(LIST_SUCCESS == iterator_get_value(&mutex->locking_thread_iter, (list_element_t *)&m));
				assert(m == mutex);
			}
			else
			{
				assert(-1 == mutex->locking_thread_id);
				assert(NULL == mutex->locking_thread_list);
			}

			if (0 < list_get_size(&mutex->wq))
			{
				iterator it;
				LIST_FOREACH(&mutex->wq, &it)
				{
					void * t;
					assert(LIST_SUCCESS == iterator_get_value(&it, (list_element_t *)&t));
					assert(NULL != t);
				}
			}
		}
	}
}

static void reset_locking(uthread_mutex_t mutex)
{
	assert((MUTEX_LOCKED == mutex->state) || (MUTEX_UNINITIALIZED == mutex->state));
	mutex->state = MUTEX_UNLOCKED;
	mutex->locking_thread_id = -1;
	mutex->locking_thread_list = NULL;
	memset(&(mutex->locking_thread_iter), 0, sizeof(mutex->locking_thread_iter));
}

static int lock_internal(thread_id tid, uthread_mutex_t m)
{
	validate_mutex(m);

	int lrc = LIST_ERROR;

	assert(NULL != m);

	/* add the mutex to the locking thread's locks list */
	m->locking_thread_list = uthread_get_locks(tid);

	lrc = list_get_head(m->locking_thread_list, &m->locking_thread_iter);
	assert(LIST_ERROR != lrc);
	lrc = list_insert_before(m->locking_thread_list, &m->locking_thread_iter,
			(list_element_t *) m);
	assert(lrc != LIST_ERROR);

	m->locking_thread_id = tid;
	m->state = MUTEX_LOCKED;

	return UTHREAD_SUCCESS;
}

static int unlock_internal(uthread_mutex_t mutex)
{
	validate_mutex(mutex);
	int lrc = LIST_ERROR;

	/* remove the mutex from the locking thread's locks list */
	lrc = list_remove(mutex->locking_thread_list, &mutex->locking_thread_iter);
	assert(lrc == LIST_SUCCESS);

	reset_locking(mutex);

	/* This thread just released the lock, add the new locking thread to the rq */
	if (0 < list_get_size(&mutex->wq))
	{
		thread_id resumed_thread_id = -1;
		uthread_resume_from_wait_queue(&mutex->wq, &resumed_thread_id);
		if (UTHREAD_SUCCESS != lock_internal(resumed_thread_id, mutex))
		{
			return MUTEX_FAILURE;
		}
	}

	return UTHREAD_SUCCESS;
}
